﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WemoConnect1
{
    public class WemoPortReader
    {
        List<int> _locations;
        IPEndPoint _remoteEp;
        byte[] _request;

        public WemoPortReader()
        {
            _locations = new List<int>();
            _remoteEp = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
            _request = CreateSsdpRequest(_remoteEp.ToString());
        }

        public int ReadPort()
        {
            var localIps = GetLocalIps();           

            foreach (var localIp in localIps)
            {
                LookIn(localIp);
            }

            return _locations.FirstOrDefault();
        }

        private void LookIn(IPAddress localIp)
        {
            using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
            {
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(_remoteEp.Address));
                socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 1);
                socket.Bind(new IPEndPoint(localIp, 0));
                socket.SendTo(_request, _remoteEp);

                Task.Factory.StartNew(() => { Thread.Sleep(1000); socket.Close(); });

                var responsebuf = new byte[51200];
                while (true)
                {
                    try
                    {
                        var ep = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
                        var len = socket.ReceiveFrom(responsebuf, ref ep);
                        var response = Encoding.UTF8.GetString(responsebuf, 0, len);
                        var location = ParseSsdpResponse(response);
                        _locations.Add(location);
                        break;
                    }
                    catch (SocketException ex)
                    {
                        if (ex.ErrorCode == 10004) break; // WSACancelBlockingCall, when the socket is closed
                    }
                }
                
            }
        }

        byte[] CreateSsdpRequest(string authority)
        {
            var request = "M-SEARCH * HTTP/1.1" + Environment.NewLine +
                          "HOST: " + authority + Environment.NewLine +
                          "ST: \"sdp:all\"" + Environment.NewLine +
                          "MAN: \"ssdp:discover\"" + Environment.NewLine +
                          "MX: 1" + Environment.NewLine +
                          "" + Environment.NewLine;
            return Encoding.UTF8.GetBytes(request);
        }

        int ParseSsdpResponse(string response)
        {
            var seperators = new char[] { (char)10, (char)13 };
            var lines = response.Split(seperators);
            var uri = (from line in lines
                    where line.ToLowerInvariant().StartsWith("location:")
                    select line.Substring(9).Trim()).FirstOrDefault();

            var uri2 = new Uri(uri);
            return uri2.Port;
        }

        private static IEnumerable<IPAddress> GetLocalIps()
        {
            var networks = from network in NetworkInterface.GetAllNetworkInterfaces()
                           where network.OperationalStatus == OperationalStatus.Up
                           where (network.NetworkInterfaceType != NetworkInterfaceType.Loopback) && (!network.Name.Contains("VMware"))
                           select network;

            var localIps =
                 from network in networks
                 let localAddr = (from uaddr in network.GetIPProperties().UnicastAddresses where uaddr.Address.AddressFamily == AddressFamily.InterNetwork select uaddr).FirstOrDefault()
                 where localAddr != null
                 select localAddr.Address;
            return localIps;
        }
    }
}
